-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local base_messages = require 'messages.base'
local log = require 'mc.logging'
local file_service = require 'service.file_service'
local file_ser = file_service.new()
local json = require 'cjson'
local prop_def = require 'macros.property_def'
local bs_util = require 'util.base_util'
local bios_factory = require 'factory.bios_factory'
local config_util = require 'handler.config_util'
local custom_messages = require 'messages.custom'
local customize_cfg_handler = require 'handler.customize_cfg'
local prop_method_app = require 'macros.property_method_app'

local export_import_engine = {}

local CURRENTVALUE_FILE_PATH<const> = "/data/opt/currentvalue.json"
local SETTING_FILE_PATH<const> = "/data/opt/setting.json"
local BIOS_CONFIG = "SystemBiosConfig"
local BIOS_SETTING_CONFIG = "SystemBiosSettingConfig"
local BMC_CONFIG = "BMCBiosConfig"
local CONFIG_DATA = "ConfigData"
local BIOS_VERSION = "BiosVersion"
local REGISTRY_VERSION = "RegistryVersion"
local TYPE_CUSTOM = 'custom'
local SYSTEM_ID<const> = 'SystemId'
local RESET_BEFORE_IMPORT = 1 << 0
local table_cache = require 'mc.table_cache'
local tbl_cache = table_cache.new()

local service_map = {}
local default_value_list = {
    ['BootSourceOverrideTarget'] = '',
    ['BootSourceOverrideEnabled'] = '',
    ['BootSourceOverrideMode'] = 0,
    ['BootModeSupport'] = 0,
    ['BootModeIpmiSettable'] = 0,
    ['Version'] = '',
    ['SKUNumber'] = '',
    ['Family'] = ''
}

local function throws_import_exception(ctx, err)
    log:operation(ctx:get_initiator(), 'BIOS', "Import BIOS configuration failed")
    error(err)
end

local function import_one_object(ctx, class_name, obj_json, system_id)
    local service = service_map[class_name]
    if not service then
        log:error("[bios]import bios json: class name(%s) no exist", class_name)
        return
    end
    service:import(ctx, obj_json, system_id)
end

local CUSTOM_SEETINGS <const> = "CustomSettings"
local BIOS_ACTIVE_CONF <const> = "BMCSet_BIOS_UpgradeActiveCondition"
local function import_bios_active_condition(config_table)
    if not config_table or not config_table[CUSTOM_SEETINGS] or
        not config_table[CUSTOM_SEETINGS][BIOS_ACTIVE_CONF] then
        return
    end

    local set_value = config_table[CUSTOM_SEETINGS][BIOS_ACTIVE_CONF]['Value']
    if not set_value then
        log:error('BMCSet_BIOS_UpgradeActiveCondition is nil, customize bios active condition failed')
        return
    end

    local obj = service_map['UpdateService']
    obj:import(set_value)
end

local function export_bios_active_condition(config_table)
    local obj = service_map['UpdateService']
    local export_value = obj:export()
    config_table[CUSTOM_SEETINGS][BIOS_ACTIVE_CONF] = export_value
    log:notice("export(BMCSet_BIOS_UpgradeActiveCondition) successfully, value is(%s)", export_value)
end

local BOOT_FLAG_VALID<const> = "BMCSet_BootFlagValidTimeoutAutoClearEnabled"
local function import_boot_flag_vaild(config_table)
    if not config_table or not config_table[CUSTOM_SEETINGS] or
        not config_table[CUSTOM_SEETINGS][BOOT_FLAG_VALID] then
        return
    end

    local set_value = config_table[CUSTOM_SEETINGS][BOOT_FLAG_VALID]['Value']
    if not set_value then
        log:error('BMCSet_BootFlagValidTimeoutAutoClearEnabled is nil, customize bios active condition failed')
        return
    end

    local obj = service_map['BootOptionService']
    obj:import(set_value)
end

local function export_boot_flag_vaild(config_table)
    local obj = service_map['BootOptionService']
    local export_value = obj:export()
    config_table[CUSTOM_SEETINGS][BOOT_FLAG_VALID] = export_value
    log:notice("export(BMCSet_BootFlagValidTimeoutAutoClearEnabled) successfully, value is(%s)", export_value)
end

function export_import_engine.test_custom_import(config_table)
    import_boot_flag_vaild(config_table)
end

function export_import_engine.test_custom_export(config_table)
    export_boot_flag_vaild(config_table)
end

-- 属性导入
local function import(ctx, class_name, object_list_json, system_id)
    if #object_list_json == 0 then
        import_one_object(ctx, class_name, object_list_json, system_id)
        return
    end
    for _, v in pairs(object_list_json) do
        import_one_object(ctx, class_name, v, system_id)
    end
end

-- 注册属性所在的业务
function export_import_engine.register_config_dealer(class_name, service)
    service_map[class_name] = service
end

local function get_config(config_string)
    local ok, bmc_config_json = pcall(function()
        return json.decode(config_string)
    end)
    if not ok or bmc_config_json == nil then
        log:error("[bios]import bios json: json format error")
        error(base_messages.MalformedJSON())
    end
    return bmc_config_json[CONFIG_DATA]
end

local function judge_bios_version(bios_config_json)
    local bios_version = bios_config_json[BIOS_VERSION]
    bios_config_json[BIOS_VERSION] = nil
    bios_config_json[REGISTRY_VERSION] = nil

    local bios_ser = bios_factory.get_service('bios_service')
    if not bios_ser then
        return
    end

    local bios_version_cmp = bios_ser:get_bios_prop('Version')
    if bios_version ~= bios_version_cmp then
        log:error('[bios] import bios josn: version is diff board(%s), file(%s)',
            bios_version_cmp, bios_version)
    end
end

local function multihost_get_import_mode_action(action)
    local bios_service = bios_factory.get_service('bios_service')
    if not bios_service then
        log:error("[bios]import bios config: bios service is nil")
        return
    end
    for _, obj in pairs(bios_service.bios_object_collection) do
        local system_id = obj.system_id
        local import_mode = bios_service:get_prop('BiosConfigActiveMode', system_id)
        if not import_mode then
            log:error("[bios]import bios config: fail to get active mode")
            return
        end
        if (import_mode & RESET_BEFORE_IMPORT) ~= 0 then
            action['BiosConfigActiveMode'] = function(ctx, ...)
                bios_service:reset_bios(ctx, system_id)
            end
        end
    end
end

local function get_import_mode_action(action)
    local bios_service = bios_factory.get_service('bios_service')
    if not bios_service then
        log:error("[bios]import bios config: bios service is nil")
        return
    end
    local import_mode = bios_service:get_prop('BiosConfigActiveMode', prop_def.DEFAULT_SYSTEM_ID)
    if not import_mode then
        log:error("[bios]import bios config: fail to get active mode")
        return
    end
    if (import_mode & RESET_BEFORE_IMPORT) ~= 0 then
        action['BiosConfigActiveMode'] = function(ctx, ...)
            bios_service:reset_bios(ctx, prop_def.DEFAULT_SYSTEM_ID)
        end
    end
end

local function import_before(ctx, config_string, import_type, is_multihost)
    local action = tbl_cache:allocate()
    if is_multihost then
        multihost_get_import_mode_action(action)
    else
        get_import_mode_action(action)
    end

    for mode, func in pairs(action) do
        func(ctx, config_string, import_type)
    end
    tbl_cache:deallocate(action)
end

local function import_process_end(ctx, bios_config_json, system_id)
    judge_bios_version(bios_config_json)
    local content = tbl_cache:allocate()
    content[prop_def.REGRIST_PROP_ATTRIBUTES] = bios_config_json
    file_ser:import_bios_json(ctx, 'Setting', json.encode(content), system_id)
    tbl_cache:deallocate(content)
end

local function get_order_config(config_string)
    local cfg_obj = json.json_object_ordered_decode(config_string)
    local bios_cfg_obj = cfg_obj[CONFIG_DATA][BIOS_CONFIG]
    bios_cfg_obj[BIOS_VERSION] = nil
    bios_cfg_obj[REGISTRY_VERSION] = nil
    local order_obj = json.json_object_new_object()
    order_obj[prop_def.REGRIST_PROP_ATTRIBUTES] = bios_cfg_obj
    return json.json_object_ordered_encode(order_obj)
end

local function import_order_config(ctx, bios_config_json, config_string)
    local ok, bios_config_string = pcall(function ()
        return get_order_config(config_string)
    end)
    if not ok or not bios_config_string then
        import_process_end(ctx, bios_config_json, prop_def.DEFAULT_SYSTEM_ID)
        return
    end
    judge_bios_version(bios_config_json)
    file_ser:import_bios_json(ctx, 'Setting', bios_config_string, prop_def.DEFAULT_SYSTEM_ID)
end

local function multihost_import_process(ctx, config)
    local bmc_config_array = config[BMC_CONFIG] or {}
    for _, bmc_config_json in pairs(bmc_config_array) do
        local system_id
        if not bmc_config_json[SYSTEM_ID] then
            system_id = prop_def.DEFAULT_SYSTEM_ID
        else
            system_id = bmc_config_json[SYSTEM_ID]['Value']
        end
        for k, v in pairs(bmc_config_json) do
            if k ~= SYSTEM_ID then
                import(ctx, k, v, system_id)
            end
        end
    end
    local bios_config_array = config[BIOS_CONFIG] or {}
    for _, bios_config_json in pairs(bios_config_array) do
        local system_id = bios_config_json[SYSTEM_ID]
        if bios_config_json == nil or next(bios_config_json) == nil then
            log:operation(ctx:get_initiator(), 'BIOS', "Import BIOS configuration successfully")
            log:info('[bios] import bios josn: no need import bios cfg')
            return
        end
        import_process_end(ctx, bios_config_json, system_id)
    end
end

local function import_process(ctx, config_string, import_type, is_multihost)
    local config = get_config(config_string)

    -- 定制化导入
    if import_type == TYPE_CUSTOM then
        customize_cfg_handler.config_import_data(config, is_multihost)
        import_bios_active_condition(config)
        import_boot_flag_vaild(config)
    end

    -- 业务导入
    if config == nil then
        log:operation(ctx:get_initiator(), 'BIOS', "Import BIOS configuration successfully")
        log:info('[bios] import bios json: no need import')
        return
    end

    if is_multihost then
        multihost_import_process(ctx, config)
    else
        local bmc_config_json = config[BMC_CONFIG] or {}
        for k, v in pairs(bmc_config_json) do
            import(ctx, k, v)
        end
        local bios_config_json = config[BIOS_CONFIG]
        if bios_config_json == nil or next(bios_config_json) == nil then
            log:operation(ctx:get_initiator(), 'BIOS', "Import BIOS configuration successfully")
            log:info('[bios] import bios josn: no need import bios cfg')
            return
        end
        import_order_config(ctx, bios_config_json, config_string)
    end
end

local function throws_export_exception(ctx)
    log:operation(ctx:get_initiator(), 'BIOS', "Export BIOS configuration failed")
    error(base_messages.InternalError())
end

-- 1、配置导入
function export_import_engine.import(ctx, config_string, import_type)
    local bios_service = bios_factory.get_service('bios_service')
    if not bios_service or not file_ser then
        log:error("[bios]import bios config: bios service is nil")
        throws_import_exception(ctx)
    end
    local is_multihost = bios_service:is_multihost()
    import_before(ctx, config_string, import_type, is_multihost)
    import_process(ctx, config_string, import_type, is_multihost)
end

local function multihost_export_bmc_cfg(config, bios_service)
    config[BMC_CONFIG] = {}
    for _, obj in pairs(bios_service.bios_object_collection) do
        local bmc_config_json = tbl_cache:allocate()
        local system_id = obj.system_id
        for class_name, service in pairs(service_map) do
            -- UpdateService和BootOptionService未纳入导入导出引擎，不能走bmc_config_json流程
            if class_name ~= 'UpdateService' and class_name ~= 'BootOptionService' then
                bmc_config_json[class_name] = service:export(system_id)
            end
        end
        bmc_config_json[SYSTEM_ID] = system_id or ''
        table.insert(config[BMC_CONFIG], bmc_config_json)
    end
end

local function export_bmc_cfg(config)
    local bmc_config_json = tbl_cache:allocate()
    for class_name, service in pairs(service_map) do
        -- UpdateService和BootOptionService未纳入导入导出引擎，不能走bmc_config_json流程
        if class_name ~= 'UpdateService' and class_name ~= 'BootOptionService' then
            bmc_config_json[class_name] = service:export()
        end
    end
    config[BMC_CONFIG] = bmc_config_json
end

local function multihost_export_bios_cfg(ctx, config, bios_service)
    config[BIOS_CONFIG] = {}
    for _, obj in pairs(bios_service.bios_object_collection) do
        local system_id = obj.system_id
        local message = file_service.get_file_json(prop_method_app.BIOS_FILE_REGISTRY_NAME, system_id)
        if message.ErrCode ~= prop_def.E_OK then
            log:notice("[bios]export bios config: system (%s) json not found, no need export", system_id)
            goto continue
        end
        local ok, message = pcall(function()
            return file_ser:export_bios_json(system_id)
        end)
        if not ok then
            log:error("[bios]export bios config: export json fail, err = %s", message)
            throws_export_exception(ctx)
        end

        local bios_config_json = message.Data
        if message.ErrCode ~= prop_def.E_OK or not bios_config_json then
            log:error("[bios]export bios config: bios json config is nil")
            throws_export_exception(ctx)
        end
        bios_config_json[BIOS_VERSION] = bios_service:get_prop('Version', system_id) or ''
        bios_config_json[REGISTRY_VERSION] = bios_service:get_prop('RegistryVersion', system_id) or ''
        bios_config_json[SYSTEM_ID] = system_id or ''
        table.insert(config[BIOS_CONFIG], bios_config_json)
        ::continue::
    end
end

local REGISTER_FILE<const> = '/data/opt/registry.json'
local function export_bios_cfg(ctx, config, bios_service)
    local bios_config_json = bs_util.get_file_json(REGISTER_FILE)
    if not bios_config_json then
        config[BIOS_CONFIG] = {}
        return
    end

    local system_id = prop_def.DEFAULT_SYSTEM_ID
    local ok, message = pcall(function()
        return file_ser:export_bios_json(system_id)
    end)
    if not ok then
        log:error("[bios]export bios config: export json fail")
        throws_export_exception(ctx)
    end

    local bios_config_json = message.Data
    if message.ErrCode ~= prop_def.E_OK or not bios_config_json then
        log:error("[bios]export bios config: bios json config is nil")
        throws_export_exception(ctx)
    end
    bios_config_json[BIOS_VERSION] = bios_service:get_prop('Version', system_id) or ''
    bios_config_json[REGISTRY_VERSION] = bios_service:get_prop('RegistryVersion', system_id) or ''
    config[BIOS_CONFIG] = bios_config_json
end

local function multihost_export_bios_setting_cfg(config, bios_service)
    config[BIOS_SETTING_CONFIG] = {}
    for _, obj in pairs(bios_service.bios_object_collection) do
        local system_id = obj.system_id
        local json_path = file_service.get_file_path(prop_method_app.BIOS_FILE_SETTING_NAME, system_id)
        local bios_config_json = bs_util.get_file_json(json_path)
        if bios_config_json then
            bios_config_json[SYSTEM_ID] = system_id or ''
            table.insert(config[BIOS_SETTING_CONFIG], bios_config_json)
        end
    end
end

local function export_bios_setting_cfg(config)
    local bios_config_json = bs_util.get_file_json(SETTING_FILE_PATH)
    if bios_config_json then
        config[BIOS_SETTING_CONFIG] = bios_config_json
    end
end

local function deallocate_array(is_multihost, config_array)
    if not config_array then
        return
    end
    if not is_multihost then
        tbl_cache:deallocate(config_array)
        return
    end
    for _, config in pairs(config_array) do
        tbl_cache:deallocate(config)
    end
end

local function export_process(ctx, bios_service, config, is_multihost)
    if is_multihost then
        -- 1、multihost生成bmc的bios配置
        local ok, err = pcall(function()
            multihost_export_bmc_cfg(config, bios_service)
        end)
        if not ok then
            log:error("[bios]export bios config: multihost export bmc config fail, err = %s", err)
            deallocate_array(is_multihost, config[BMC_CONFIG])
            tbl_cache:deallocate(config)
            throws_export_exception(ctx)
        end
        -- 2、multihost生成带内的bios配置
        multihost_export_bios_cfg(ctx, config, bios_service)  -- multihost导出当前生效的配置
        multihost_export_bios_setting_cfg(config, bios_service) -- multihost导出下发未生效的配置
    else
        -- 1、生成bmc的bios配置
        local ok, err = pcall(function()
            export_bmc_cfg(config)
        end)
        if not ok then
            log:error("[bios]export bios config: export bmc config fail, err = %s", err)
            deallocate_array(is_multihost, config[BMC_CONFIG])
            tbl_cache:deallocate(config)
            throws_export_exception(ctx)
        end
        -- 2、生成带内的bios配置
        export_bios_cfg(ctx, config, bios_service)  -- 导出当前生效的配置
        export_bios_setting_cfg(config) -- 导出下发未生效的配置
    end
end

local function get_bios_config_obj(bios_config_json)
    local bios_new_config_obj = json.json_object_new_object()
    local bios_config_obj = bs_util.get_file_order_json(CURRENTVALUE_FILE_PATH)
    if not bios_config_obj then
        return bios_new_config_obj
    end
    for k, v in pairs(bios_config_obj) do
        if bios_config_json[k] ~= nil then
            bios_new_config_obj[k] = v
        end
    end
    bios_new_config_obj[BIOS_VERSION] = bios_config_json[BIOS_VERSION]
    bios_new_config_obj[REGISTRY_VERSION] = bios_config_json[REGISTRY_VERSION]
    return bios_new_config_obj
end

local function order_export_bios_config(final_config, bios_config_json)
    local final_string = json.encode(final_config)
    local final_obj
    local ok, res = pcall(function ()
        final_obj = json.json_object_ordered_decode(final_string)
        final_obj[CONFIG_DATA][BIOS_CONFIG] = get_bios_config_obj(bios_config_json)
        final_obj[CONFIG_DATA][BIOS_SETTING_CONFIG] = bs_util.get_file_order_json(SETTING_FILE_PATH)
    end)
    if not ok then
        log:error('export order config failed, err = %s', res)
        return final_string
    end
    return json.json_object_ordered_encode(final_obj)
end

-- 配置导出
function export_import_engine.export(ctx, export_type)
    local config = tbl_cache:allocate()
    local bios_service = bios_factory.get_service('bios_service')
    if not bios_service or not file_ser then
        log:error("[bios]export bios config: bios service is nil")
        throws_export_exception(ctx)
    end

    local is_multihost = bios_service:is_multihost()

    export_process(ctx, bios_service, config, is_multihost)

    -- 3、生成最终配置
    local final_config = tbl_cache:allocate()
    final_config[CONFIG_DATA] = config

    -- 根据定制化映射关系处理 config_string 
    if export_type == TYPE_CUSTOM then
        customize_cfg_handler.config_export_data(final_config[CONFIG_DATA], is_multihost)
        export_bios_active_condition(final_config[CONFIG_DATA])
        export_boot_flag_vaild(final_config[CONFIG_DATA])
    end

    local ok, user_config_data = pcall(function()
        if is_multihost then
            return json.encode(final_config)
        else
            return order_export_bios_config(final_config, config[BIOS_CONFIG])
        end
    end)
    if not ok or user_config_data == nil then
        log:error("[bios]export bios config: bios json config is nil")
        deallocate_array(is_multihost, config[BMC_CONFIG])
        tbl_cache:deallocate(config)
        tbl_cache:deallocate(final_config)
        throws_export_exception(ctx)
    end
    deallocate_array(is_multihost, config[BMC_CONFIG])
    tbl_cache:deallocate(config)
    tbl_cache:deallocate(final_config)
    log:operation(ctx:get_initiator(), 'BIOS', "Export BIOS configuration successfully")
    return user_config_data
end

function export_import_engine.import_sevice_cfg(ctx, obj_prop_list, service, class_name, prop_map, system_id)
    if not system_id then
        system_id = prop_def.DEFAULT_SYSTEM_ID
    end
    for prop, val_json in pairs(obj_prop_list) do
        local import_fun = prop_map[prop]
        if import_fun == nil then
            log:error('[bios] import bios config: get property(%s) fun fail', prop)
            goto continue
        end

        local current_bmc_form_val = service:get_prop(config_util.get_bmc_prop(prop), system_id)
        local current_user_form_val = config_util.get_user_prop_val(prop, current_bmc_form_val)
        -- 1、属性名转换 2、属性值转换
        if prop == 'StartOptionFlag' or config_util.is_need_import(val_json, current_user_form_val) then
            local user_import_val = config_util.get_bmc_prop_val(prop, val_json['Value'])
            local ok , _ = pcall(function()
                return import_fun(service, ctx, user_import_val, system_id)
            end)
            if not ok then
                local err_msg = string.format("/%s/%s/%s", class_name, class_name, prop)
                error(custom_messages.CollectingConfigurationErrorDesc(err_msg))
            end
        end
        ::continue::
    end
end

function export_import_engine.export_sevice_cfg(export_list, service, system_id)
    if not system_id then
        system_id = prop_def.DEFAULT_SYSTEM_ID
    end
    service.export_data_collection[system_id] = {}
    for user_prop_name, bmc_prop_name in pairs(export_list) do
        local ok, _ = pcall(function()
            service.export_data_collection[system_id][user_prop_name] =
                config_util.get_user_prop_val(user_prop_name, service:get_prop(bmc_prop_name, system_id)) or
                    default_value_list[bmc_prop_name]
        end)
        if not ok then
            log:error("[bios]export bios config: export property(%s) fail", bmc_prop_name)
            error(base_messages.InternalError())
        end
    end
    return service.export_data_collection[system_id]
end

return export_import_engine